package org.sninwo.lnbook.book.service;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import org.apache.commons.lang.xwork.StringUtils;
import org.sninwo.lnbook.base.Page;
import org.sninwo.lnbook.book.dao.BookDao;
import org.sninwo.lnbook.book.entity.Book;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class SearchManager {
	//-- 断句分隔符 --//
	private static final String SEPARATORS = ".?!\n\r\t。？！　…";
	//在搜索结果中，对书籍描述的句子的最大长度
	private static final int RESULT_SENTENCES_MAX_LEN = 150;
	
	@Resource private BookDao bookDao;

	/**
	 * 通过关键词全文查找书籍记录，如果有多个关键词，先考虑用and来联合查询，如果找不到记录再尝试用or来查询。
	 * 查询之后，在书籍的内容提要、序跋等字段中找出一部分包括关键词的句子，添加到返回结果中。
	 * 返回结果中，列表的元素为Map，有3个key，分别为book(Book对象)、type(BookType对象)和bookDesc(部分包含关键词的句子)
	 */
	@Transactional(readOnly = true) //不加上这句，搜索时会出错，搞不懂。。。
	public Page<Map<String, Object>> searchBook(Page<Map<String, Object>> page, String[] words){
		page = bookDao.searchBook(page, words);
		for (Map<String, Object> item : page.getResult()){
			Book book = (Book)item.get("book");
			item.put("bookDesc", findKeyWordSentences(book, words));
		}
		return page;
	}
	
	/**
	 * 从Book对象的内容提要、序跋等属性中，找出部分包括关键词的句子
	 */
	private String findKeyWordSentences(Book book, String[] words){
		List<String> sentences = new ArrayList<String>();
		appendSentence(sentences, book.getAbstract_(), words);
		appendSentence(sentences, book.getPreface(), words);
		appendSentence(sentences, book.getAboutAuthor(), words);
		appendSentence(sentences, book.getChorography(), words);
		appendSentence(sentences, book.getHoldings(), words);
		appendSentence(sentences, book.getComment(), words);
		String result = StringUtils.join(sentences, "...");
		if (result.length() > RESULT_SENTENCES_MAX_LEN){ //去掉超出长度限制的部分
			result = result.substring(0, RESULT_SENTENCES_MAX_LEN) + "...";
		}
		return result;
	}
	
	private void appendSentence(List<String> sentences, String content, String[] words){
		String sentence = findSentencesByWords(content, words);
		if (sentence != null)
			sentences.add(sentence);
	}

	/**
	 * 从一段文字中，找出含有各个关键词的句子，并这将这些句子合并起来。
	 * 句子不能重复，多条句子之间用空格分开。
	 * @param content 一段文字
	 * @param words 关键词数组
	 * @return 含有各个关键词的句子的组合
	 */
	private String findSentencesByWords(String content, String[] words){
		Set<String> lines = new LinkedHashSet<String>();
		for (String wd : words){
			String line = findSentenceByWord(content, wd);
			if (line != null){
				lines.add(line);
			}
		}
		return lines.size() > 0 ? StringUtils.join(lines, " ") : null;
	}
	
	/**
	 * 从一段文字中，找出第一个含有关键词的句子。
	 * @param content 一段文字
	 * @param word 关键词
	 * @return 含有关键词的第一个句子
	 */
	private String findSentenceByWord(String content, String word){
		if (content == null || word == null || word.length() == 0){
			return null;
		}
		int i = content.indexOf(word);
		if (i == -1)
			return null;
		int start = i, end = i + word.length() - 1;
		while (start >= 0 && !isSeparators(content.charAt(start))) start--;
		while (end < content.length() - 1 && !isSeparators(content.charAt(end))) end ++;
		return content.substring(start + 1, end + 1);
	}
	
	/**
	 * 判断字符是否是句子分隔符
	 */
	private boolean isSeparators(char c){
		return SEPARATORS.indexOf(c) >= 0;
	}
	
	/**
	 * 从书籍中找出对其它书籍的引用列表
	 * @param book
	 * @return
	 */
	@Transactional(readOnly = true)
	public List<Book> findBookRefers(Long bookId){
		if (bookId == null){
			return null;
		}
		List<String> bookNames = findReferNames(bookId);
		List<Book> bookRefers = new ArrayList<Book>(bookNames.size());
		for (String bookName : bookNames){
			Long referBookId = bookDao.findBookIdByName(bookName);
			if (referBookId != null && referBookId != 0){
				Book referBook = new Book();
				referBook.setId(referBookId);
				referBook.setName(bookName);
				bookRefers.add(referBook);
			}
		}
		return bookRefers;
	}
	
	/**
	 * 从书籍的大文本字段内容中找出其它书籍的书名
	 */
	private List<String> findReferNames(Long bookId){
		Book book = bookDao.get(bookId);
		String text = book.getTextContent();
		List<String> bookNames = new ArrayList<String>();
		int start = 0, end = 0;
		while(true){
			start = text.indexOf("《", start);
			if (start == -1) return bookNames;
			end = text.indexOf("》", start + 1);
			if (end == -1) return bookNames;
			String bookName = text.substring(start + 1, end);
			if (!bookName.equals(book.getName()) && !bookNames.contains(bookName))
				bookNames.add(bookName);
			start = end + 1;
		}
	}
}
